package task

import (
	"errors"
	"github.com/hibiken/asynq"
	"github.com/spf13/viper"
	"time"
)

var taskClient *asynq.Client // 异步任务客户端

// AddTask 添加asynq任务、延迟队列(任务)、分布式任务、定时(不循环)任务
//
//	 使用如下：
//		gin_frame.AddTask(gin_frame.Task{ // 任务是立即被执行,没有延迟队列效果
//			Type:    "task:welcome",
//			Payload: []byte("{\"name\":\"黄志林\",\"age\":30}"),
//		})
//
//		gin_frame.AddTask(gin_frame.Task{ // 延迟队列(任务)
//			Type:      "task:welcome",
//			Payload:   []byte("{\"name\":\"黄志林\",\"age\":30}"),
//			ProcessIn: 10 * time.Second,
//		})
//
//		gin_frame.AddTask(gin_frame.Task{ // 延迟队列(任务)、定时任务
//			Type:      "task:weTasks",
//			Payload:   []byte("{\"name\":\"黄志林\",\"age\":30}"),
//			ProcessAt: time.Now().Add(15 * time.Second),
//		})
func AddTask(t Task) (*asynq.TaskInfo, error) {
	if open, _ := ServiceIsOpen(); !open {
		return nil, errors.New("处理任务服务未开启，请执行：go run main.go task 命令进行开启操作")
	}

	if taskClient == nil {
		taskClient = asynq.NewClient(asynq.RedisClientOpt{
			Addr:     viper.GetString("task.redis.addr"),
			Password: viper.GetString("task.redis.password"),
			DB:       viper.GetInt("task.redis.db"),
		})
	}

	newTask := &asynq.Task{}
	if t.Type == "" {
		return nil, errors.New("任务名称(type) 不能为空~")
	}
	newTask = asynq.NewTask(t.Type, t.Payload) // 创建任务

	// 任务入队
	if t.ProcessIn != 0 {
		return taskClient.Enqueue(newTask, asynq.ProcessIn(t.ProcessIn))
	}

	if !t.ProcessAt.IsZero() {
		return taskClient.Enqueue(newTask, asynq.ProcessAt(t.ProcessAt))
	}

	return taskClient.Enqueue(newTask)
}

// ServiceIsOpen 处理任务服务 是否开启
func ServiceIsOpen() (bool, error) {
	inspector := asynq.NewInspector(asynq.RedisClientOpt{
		Addr:     viper.GetString("task.redis.addr"),
		Password: viper.GetString("task.redis.password"),
		DB:       viper.GetInt("task.redis.db"),
	})
	defer inspector.Close()

	if services, err := inspector.Servers(); err == nil {
		if len(services) > 0 {
			return true, nil
		}
		return false, nil
	} else {
		return false, err
	}
}

// CronIsOpen 计划(周期)任务服务 是否开启
func CronIsOpen() (bool, error) {
	// 获取 计划(周期)任务服务 列表，然后判断 系统预留的周期计划任务(cron:ping) 是否存在来判断是否开启了
	inspector := asynq.NewInspector(asynq.RedisClientOpt{
		Addr:     viper.GetString("task.redis.addr"),
		Password: viper.GetString("task.redis.password"),
		DB:       viper.GetInt("task.redis.db"),
	})
	defer inspector.Close()

	if entriyList, err := inspector.SchedulerEntries(); err == nil {
		isHasCronPing := false
		for _, entry := range entriyList {
			if entry.Task.Type() == "cron:ping" {
				isHasCronPing = true
				break
			}
		}
		if isHasCronPing {
			return true, nil
		}
		return false, nil
	} else {
		return false, err
	}
}

// Task asynq任务项
// ProcessIn 和 ProcessAt 如果都为空的话，任务将立即进入待办队列，如果空闲，则会立即得到执行
// 如果需要实现延迟队列效果的话，这2项二选一即可
// 如果同时存在的话，以ProcessIn为准，ProcessAt会被忽略
type Task struct {
	Type      string        // 必填，任务名称(标识)，如：放送电子邮箱，可以定义任务名称为'task:sendEmail'
	Payload   []byte        // 选填，任务数据或参数，json字节
	ProcessIn time.Duration // 选填，延时队列，比如：10*time.Second,表示在10秒后执行，
	ProcessAt time.Time     // 选填，定时任务，指定在具体某个时间点执行， 比如： time.Now().Add(15*time.Second),表示在10秒后执行
}
